/**************************************************************************************************
	$Header: /pub/cvsroot/mydns/src/mydns/import.c,v 1.8 2002/07/14 22:16:28 bboy Exp $
	import.c: Import AXFR data from a foreign nameserver.

	Copyright (C) 2002  Don Moore <bboy@bboy.net>

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at Your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
**************************************************************************************************/

#include "named.h"

extern void db_connect(void);									/* from main.c */

static u_char *hostname, *zone;								/* Hostname of remote host and zone */
static MYDNS_SOA *soa;											/* SOA record for zone being imported */
static int imported;												/* Number of records imported */


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	IMPORT_CONNECT
	Connects to the remote server specified by `arg' (format "HOST[:PORT]/ZONE") and return a
	fd to newly created socket.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static int
import_connect(char *arg, u_char **hostnamep, u_char **zonep)
{
	u_char *hostname, *zone, *portp;
	u_int	port = 53;
	int	fd;
	struct hostent *he;
	struct sockaddr_in sa;

	if (!(hostname = arg) || !*hostname)					/* Parse hostname, port, zone */
		Errx(_("host not specified"));
	if ((zone = strchr(arg, '/')))
		*zone++ = '\0';
	if (!zone || !*zone)
		Errx(_("zone not specified"));
	if ((portp = strchr(arg, ':')))
		*portp++ = '\0', port = atoi(portp);
	strtrim(zone);

	/* REMOVE any trailing dot(s) from end of zone name.. We automatically append the dot in
		request_axfr */
	while (LASTCHAR(zone) == '.')
		LASTCHAR(zone) = '\0';

	if (strlen(zone) > 256)
		Errx(_("zone too long"));

	*hostnamep = hostname;
	*zonep = zone;

	Verbose(_("importing `%s' from %s:%u"), zone, hostname, port);

	memset(&sa, 0, sizeof(struct sockaddr_in));
	sa.sin_family = AF_INET;
	sa.sin_port = htons(port);
	if (!(he = gethostbyname(hostname)))
		Errx("%s: %s", hostname, _("unknown host"));
	memcpy(&sa.sin_addr, he->h_addr_list[0], sizeof(struct in_addr));

	if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
		Err("%s", hostname);
	if (connect(fd, (const struct sockaddr *)&sa, sizeof(struct sockaddr_in)))
		Err("%s", hostname);
#if DEBUG
	Debug(_("%s: connected (port %u)"), hostname, port);
#endif
	return (fd);
}
/*--- import_connect() --------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	MAKE_QUESTION
	Creates a question.  Returns the packet and stores the length of the packet in `packetlen'.
	The packet is dynamically allocated and should be free()'d.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
u_char *
make_question(u_int16_t id, dns_qtype_t qtype, u_char *name, size_t *packetlen)
{
	u_char req[1024], *dest = req, *c;
	DNS_HEADER	header;
	size_t len;

	if (packetlen) *packetlen = 0;

	memset(&header, 0, sizeof(DNS_HEADER));
	DNS_PUT16(dest, id);											/* ID */
	header.rd = 1;
	memcpy(dest, &header, sizeof(DNS_HEADER)); dest += SIZE16;
	DNS_PUT16(dest, 1);											/* QDCOUNT */
	DNS_PUT16(dest, 0);											/* ANCOUNT */
	DNS_PUT16(dest, 0);											/* NSCOUNT */
	DNS_PUT16(dest, 0);											/* ARCOUNT */
	for (c = name; *c; c++)										/* QNAME */
		if (c == name || *c == '.')
		{
			u_char *end;
			if (c != name)
				c++;
			if ((end = strchr(c, '.')))
				*end = '\0';
			if ((len = strlen(c)))
			{
				if (len > 64)
				{
					Warnx(_("zone contains invalid label (64 chars max)"));
					return (NULL);
				}
				*dest++ = len;
				DNS_PUT(dest, c, len);
			}
			if (end)
				*end = '.';
		}
	*dest++ = 0;
	DNS_PUT16(dest, (u_int16_t)qtype);						/* QTYPE */
	DNS_PUT16(dest, DNS_C_IN);									/* QCLASS */
	len = dest - req;

	if (packetlen) *packetlen = len;

	c = MALLOC(len);
	memcpy(c, &req, len);

	return (c);
}
/*--- make_question() ---------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	REQUEST_AXFR
	Constructs and sends the AXFR request packet.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static void
request_axfr(int fd, u_char *hostname, u_char *zone)
{
	u_char *qb, *q, *p;
	size_t qlen;
	int	 rv, off = 0;

	if (!(qb = make_question(getpid(), DNS_T_AXFR, zone, &qlen)))
		exit(EXIT_FAILURE);
	p = q = MALLOC(qlen + SIZE16);
	DNS_PUT16(p, qlen);
	memcpy(p, qb, qlen);
	free(qb);
	qlen += SIZE16;
	do
	{
		if ((rv = write(fd, q + off, qlen - off)) < 0)
			Err("%s", hostname);
		off += rv;
	} while (off < qlen);
	free(q);
}
/*--- request_axfr() ----------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	SQLQUERY
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static void
sqlquery(const char *fmt, ...)
{
	va_list ap;
	char query[BUFSIZ];

	va_start(ap, fmt);
	vsnprintf(query, sizeof(query), fmt, ap);
	va_end(ap);

	if (mysql_query(mysql, query))
		WarnSQL(mysql, _("error importing"));
	imported++;
}
/*--- sqlquery() --------------------------------------------------------------------------------*/


#define SQLESC(s,d) { \
		u_char *rv = alloca(strlen((s))*2+1); \
		mysql_real_escape_string(mysql, rv, s, strlen((s))); \
		d = rv; \
	}

/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	PROCESS_AXFR_SOA
	Find the SOA.  Insert it, and return the SOA record.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static void
process_axfr_soa(u_char *origin, u_char *reply, size_t replylen, u_char *src, u_int32_t ttl)
{
	u_char ns[DNS_MAXNAMELEN], mbox[DNS_MAXNAMELEN];
	u_char *xorigin, *xns, *xmbox;
	u_int32_t serial, refresh, retry, expire, minimum;

	if (soa)
		return;

	if (!(src = name_unencode(reply, replylen, src, ns, sizeof(ns))))
		Errx("%s SOA: %s: %s", origin, _("error reading ns from SOA"), origin);
	if (!(src = name_unencode(reply, replylen, src, mbox, sizeof(mbox))))
		Errx("%s SOA: %s: %s", origin, _("error reading mbox from SOA"), origin);
	DNS_GET32(serial, src);
	DNS_GET32(refresh, src);
	DNS_GET32(retry, src);
	DNS_GET32(expire, src);
	DNS_GET32(minimum, src);
	if (ttl < minimum)
		ttl = minimum;

	SQLESC(origin, xorigin);
	SQLESC(ns, xns);
	SQLESC(mbox, xmbox);

	/* Check for existing SOA that matches */
	if (mydns_soa_load(mysql, &soa, origin) != 0)
		WarnSQL(mysql, "%s SOA: %s", origin, _("error locating SOA"));
	if (soa)			/* Update existing SOA */
	{
		sqlquery(
			"UPDATE " MYDNS_SOA_TABLE " SET "
			"ns='%s',mbox='%s',serial=%u,refresh=%u,retry=%u,expire=%u,minimum=%u,ttl=%u "
			"WHERE id=%u",
			xns, xmbox, serial, refresh, retry, expire, minimum, ttl, soa->id);
		mydns_soa_free(soa);
	}
	else				/* Insert new SOA */
	{
		sqlquery(
			"INSERT INTO " MYDNS_SOA_TABLE
			" (origin,ns,mbox,serial,refresh,retry,expire,minimum,ttl)"
			" VALUES ('%s','%s','%s',%u,%u,%u,%u,%u,%u)",
			xorigin, xns, xmbox, serial, refresh, retry, expire, minimum, ttl);
	}

	/* Load the SOA */
	if (mydns_soa_load(mysql, &soa, origin) != 0)
		WarnSQL(mysql, "%s SOA: %s", origin, _("error loading SOA"));

#if DEBUG
	Debug(_("SOA `%s' (%u) ok"), soa->origin, soa->id);
#endif
}
/*--- process_axfr_soa() ------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	SHORTNAME
	Removes the origin from a name if it is present.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static u_char *
shortname(MYDNS_SOA *soa, u_char *name)
{
	size_t nlen = strlen(name), olen = strlen(soa->origin);

	if (nlen < olen)
		return (name);
	if (!strcasecmp(soa->origin, name))
		return ("");
	if (!strcasecmp(name + nlen - olen, soa->origin))
		name[nlen - olen - 1] = '\0';
	return (name);
}
/*--- shortname() -------------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	PROCESS_AXFR_ANSWER
	Processes a single answer.  If it's a SOA record, it is inserted, loaded, and the SOA record
	is returned.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static u_char *
process_axfr_answer(u_char *reply, size_t replylen, u_char *src)
{
	u_char name[DNS_MAXNAMELEN], data[DNS_MAXNAMELEN], *rv, *xname, *xdata;
	u_int16_t type, class, rdlen;
	u_int32_t ttl;

	if (!(src = name_unencode(reply, replylen, src, name, sizeof(name))))
		Errx("%s: %s: %s", hostname, _("error reading name from answer section"), name);

	DNS_GET16(type, src);
	DNS_GET16(class, src);
	DNS_GET32(ttl, src);
	DNS_GET16(rdlen, src);
	rv = src + rdlen;

	if (!soa && type != DNS_T_SOA)
		Errx(_("got non-SOA RR before SOA"));

	switch (type)
	{
		case DNS_T_SOA:
			if (soa)
				return (NULL);
			process_axfr_soa(name, reply, replylen, src, ttl);
			break;

		case DNS_T_PTR:
			{
				struct in_addr addr;
				addr.s_addr = mydns_revstr_ip4(name);
				if (!(src = name_unencode(reply, replylen, src, data, sizeof(data))))
					Errx("%s PTR: %s: %s", name, _("error reading data"), data);
				SQLESC(data, xdata);
				sqlquery(
					"REPLACE INTO "MYDNS_PTR_TABLE" (ip,name,ttl) VALUES "
					"(INET_ATON('%s'),'%s',%u)", inet_ntoa(addr), xdata, ttl);
				Verbose("%s PTR %s", inet_ntoa(addr), shortname(soa, data));
			}
			break;

		case DNS_T_A:
			{
				struct in_addr addr;
				memcpy(&addr.s_addr, src, SIZE32);
				SQLESC(shortname(soa, name), xname);
				sqlquery(
					"REPLACE INTO "MYDNS_RR_TABLE" (zone,name,type,data,ttl) VALUES "
					"(%u,'%s','A','%s',%u)",
					soa->id, xname, inet_ntoa(addr), ttl);
				Verbose("%s A %s", shortname(soa, name), inet_ntoa(addr));
			}
			break;

#if HAVE_IPV6
		case DNS_T_AAAA:
			{
				struct in6_addr addr;
				memcpy(&addr, src, sizeof(u_int8_t) * 16);
				inet_ntop(AF_INET6, &addr, data, sizeof(u_int8_t) * 16);
				SQLESC(shortname(soa, name), xname);
				sqlquery(
					"REPLACE INTO "MYDNS_RR_TABLE" (zone,name,type,data,ttl) VALUES "
					"(%u,'%s','AAAA','%s',%u)",
					soa->id, xname, data, ttl);
				Verbose("%s AAAA %s", shortname(soa, name), data);
			}
			break;
#endif

		case DNS_T_CNAME:
			if (!(src = name_unencode(reply, replylen, src, data, sizeof(data))))
				Errx("%s CNAME: %s: %s", name, _("error reading data"), data);
			SQLESC(shortname(soa, name), xname);
			SQLESC(data, xdata);
			sqlquery(
				"REPLACE INTO "MYDNS_RR_TABLE" (zone,name,type,data,ttl) VALUES "
				"(%u,'%s','CNAME','%s',%u)",
				soa->id, xname, xdata, ttl);
			Verbose("%s CNAME %s", shortname(soa, name), shortname(soa, data));
			break;

		case DNS_T_MX:
			{
				u_int16_t pref;
				DNS_GET16(pref, src);
				if (!(src = name_unencode(reply, replylen, src, data, sizeof(data))))
					Errx("%s MX: %s: %s", name, _("error reading data"), data);
				SQLESC(shortname(soa, name), xname);
				SQLESC(data, xdata);
				sqlquery(
					"REPLACE INTO "MYDNS_RR_TABLE" (zone,name,type,data,aux,ttl) VALUES "
					"(%u,'%s','MX','%s',%d,%u)",
					soa->id, xname, xdata, pref, ttl);
				Verbose("%s MX %d %s", shortname(soa, name), pref, shortname(soa, data));
			}
			break;

		case DNS_T_NS:
			if (!(src = name_unencode(reply, replylen, src, data, sizeof(data))))
				Errx("%s NS: %s: %s", name, _("error reading data"), data);
			SQLESC(shortname(soa, name), xname);
			SQLESC(data, xdata);
			sqlquery(
				"REPLACE INTO "MYDNS_RR_TABLE" (zone,name,type,data,ttl) VALUES "
				"(%u,'%s','NS','%s',%u)",
				soa->id, xname, xdata, ttl);
			Verbose("%s NS %s", shortname(soa, name), shortname(soa, data));
			break;

		case DNS_T_TXT:
			{
				size_t len = *src++;

				memcpy(data, src, len);
				data[len] = '\0';
				SQLESC(shortname(soa, name), xname);
				SQLESC(data, xdata);
				sqlquery(
					"REPLACE INTO "MYDNS_RR_TABLE" (zone,name,type,data,ttl) VALUES "
					"(%u,'%s','TXT','%s',%u)",
					soa->id, xname, xdata, ttl);
				Verbose("%s TXT \"%s\"", shortname(soa, name), data);
			}
			break;

		default:
			Warnx("%s %s: %s", name, mydns_qtype_str(type), _("discarding unsupported RR type"));
			break;
	}
	return (rv);
}
/*--- process_axfr_answer() ---------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	PROCESS_AXFR_REPLY
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static int
process_axfr_reply(u_char *reply, size_t replylen)
{
	u_char *src = reply, name[DNS_MAXNAMELEN];
	u_int16_t n, qdcount, ancount;
	DNS_HEADER	header;

	/* Read packet header */
	src += SIZE16;					/* ID */
	memcpy(&header, src, SIZE16); src += SIZE16;
	DNS_GET16(qdcount, src);
	DNS_GET16(ancount, src);
	src += SIZE16 * 2;
	if (header.rcode != DNS_R_OK)
		Errx("%s: %s", hostname, mydns_rcode_str(header.rcode));

	/* Read question section(s) */
	for (n = 0; n < qdcount; n++)
	{
		if (!(src = name_unencode(reply, replylen, src, name, sizeof(name))))
			Errx("%s: %s: %s", hostname, _("error reading name from question section"), name);
		src += (SIZE16 * 2);
	}

	/* Process all RRs in the answer section */
	for (n = 0; n < ancount; n++)
		if (!(src = process_axfr_answer(reply, replylen, src)))
			return (-1);
	return (0);
}
/*--- process_axfr_reply() ----------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	IMPORT
	The format of `arg' is "HOST:PORT/ZONE".
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
void
import(char *arg)
{
	u_char *reply, l[2];
	int fd;
	size_t replylen;

	hostname = zone = NULL;
	soa = NULL;

	db_connect();													/* Connect to database */
	fd = import_connect(arg, &hostname, &zone);			/* Connect to remote host */
	request_axfr(fd, hostname, zone);						/* Send AXFR request */

	/* Read packets from server and process them */
	while (recv(fd, l, 2, MSG_WAITALL) == 2)
	{
		if ((replylen = ((l[0] << 8) | (l[1]))) < 12)
			Errx(_("message too short"));
		reply = MALLOC(replylen);
		if (recv(fd, reply, replylen, MSG_WAITALL) != replylen)
			Errx(_("short message from server"));
		if (process_axfr_reply(reply, replylen))
			break;
		free(reply);
	}
	Notice(_("Zone `%s': %u resource record(s) imported"), zone, imported);
}
/*--- import() ----------------------------------------------------------------------------------*/

/* vi:set ts=3: */
/* NEED_PO */
